/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { BaseAnalyzeResult, isHarModule, Logger, PluginFileUtil } from '../../framework';
import { HMRouterResult, RouterInfo } from '../model';
import { AnnotationConstants, FilePathConstants, PrefixConstants, RouterMapConstants } from '../constants';
import { HMRouterExtensionContext } from '../HMRouterExtensionContext';
import { ErrorCode, PluginError } from '../error/PluginError';

export class RouterMapBuildingProcessor {
  private context: HMRouterExtensionContext;

  constructor(context: HMRouterExtensionContext) {
    this.context = context;
  }

  execute(): void {
    Logger.debug(this.context.node.getNodeName(), `Start to build router map...`);

    // 1. Build the routerMap based on the analysis results
    for (const result of this.context.getAnalyzeResults()) {
      this.buildRouterMap(result);
    }

    const isHar = isHarModule(this.context.node);

    if ((isHar && !this.context.routerMap) || (isHar && this.context.routerMap.length === 0)) {
      Logger.info(this.context.node.getNodeName(), `Skip generating routerMap`);
      return;
    }

    // 2. Read the existing routerMap and add it to the context
    this.readExistingRouterMap();

    // 3. Generate the routerMap file
    this.generateRouterMapFile();
  }

  private buildRouterMap(result: BaseAnalyzeResult): void {
    if (!result.sourceFilePath) {
      throw PluginError.create(ErrorCode.ERROR_PAGE_SOURCE_FILE, this.context.config.moduleName, result.sourceFilePath);
    }

    let pageSourceFile = this.context.config
      .getRelativeSourcePath(result.sourceFilePath)
      .replaceAll(FilePathConstants.FILE_SEPARATOR, FilePathConstants.DELIMITER);

    // Special processing for HMRouter annotations
    if (result.annotation === AnnotationConstants.ROUTER_ANNOTATION) {
      let pageUrl = (result as HMRouterResult).pageUrl;
      // 使用组件名称+pageUrl作为唯一键获取生成的文件路径，避免重名组件冲突
      let uniqueKey = `${result.name}#${pageUrl}`;
      let builderPath = this.context.generatedPaths.get(uniqueKey) || '';
      this.context.routerMap.push({
        name: pageUrl,
        pageSourceFile: builderPath,
        buildFunction: result.name + 'Builder',
        customData: result,
      });
      return;
    }

    this.useAnnotationConfig(result, pageSourceFile);
  }

  private useAnnotationConfig(result: BaseAnalyzeResult, pageSourceFile: string): void {
    // Use mapping table to handle other annotation types
    const annotationConfig = {
      [AnnotationConstants.ANIMATOR_ANNOTATION]: {
        prefix: PrefixConstants.ANIMATOR_PREFIX,
        propertyName: 'animatorName',
        resultType: 'HMAnimatorResult',
      },
      [AnnotationConstants.INTERCEPTOR_ANNOTATION]: {
        prefix: PrefixConstants.INTERCEPTOR_PREFIX,
        propertyName: 'interceptorName',
        resultType: 'HMInterceptorResult',
      },
      [AnnotationConstants.LIFECYCLE_ANNOTATION]: {
        prefix: PrefixConstants.LIFECYCLE_PREFIX,
        propertyName: 'lifecycleName',
        resultType: 'HMLifecycleResult',
      },
      [AnnotationConstants.SERVICE_ANNOTATION]: {
        prefix: PrefixConstants.SERVICE_PREFIX,
        propertyName: 'serviceName',
        resultType: 'HMServiceResult',
      },
      [AnnotationConstants.SERVICE_PROVIDE_ANNOTATION]: {
        prefix: PrefixConstants.SERVICE_PROVIDE_PREFIX,
        propertyName: 'serviceName',
        resultType: 'HMServiceResult',
      },
    };
    const config = annotationConfig[result.annotation as keyof typeof annotationConfig];
    const nameValue = (result as any)[config.propertyName];
    const fullName = config.prefix + nameValue;
    this.context.routerMap.push({
      name: fullName,
      pageSourceFile: pageSourceFile,
      buildFunction: '',
      customData: result,
    });
  }

  // Generate the route table file
  private generateRouterMapFile(): void {
    let set = new Set<string>();
    this.context.routerMap.forEach((item) => {
      // Check if the route name is duplicated
      if (set.has(item.name)) {
        throw PluginError.create(ErrorCode.DUPLICATE_NAME, this.context.config.moduleName, item.name);
      }
      set.add(item.name);
    });

    let routerMap = {
      routerMap: this.context.routerMap.map((item) => {
        //Delete annotation
        if (item.customData && item.customData.annotation) {
          delete item.customData.annotation;
          delete item.customData.sourceFilePath;
          delete item.customData.isDefaultExport;
          delete item.customData.templateData;
        }
        return item;
      }),
    };

    // Get the route mapping path
    const routerMapJsonStr = JSON.stringify(routerMap, null, 2);
    const routerMapFilePath = this.context.config.getRouterMapDir();
    // Ensure that the file exists
    PluginFileUtil.ensureFileSync(routerMapFilePath);
    PluginFileUtil.writeFileSync(routerMapFilePath, routerMapJsonStr);
    Logger.info(this.context.node.getNodeName(), `hm_router_map.json has been generated in ${routerMapFilePath}`);
  }

  // Read the existing routerMap and add it to the context
  private readExistingRouterMap(): void {
    // Get the module JSON configuration
    const moduleJsonOpt = this.context.moduleContext.getModuleJsonOpt();

    // Check if the configuration contains routerMap
    if (!moduleJsonOpt?.module.routerMap) {
      Logger.info(this.context.node.getNodeName(), `No routerMap found in the module configuration`);
      return;
    }
    // Get the routerMap filename
    const routerMapFileName = moduleJsonOpt.module.routerMap.split(':')[1];

    // If it isn't the routerMap we generated, we need to merge it
    if (routerMapFileName !== 'hm_router_map') {
      // Get the routerMap path
      const routerMapFilePath = this.context.config.getModuleRouterMapFilePath(routerMapFileName!);
      const routerMapObj = PluginFileUtil.readJson5(routerMapFilePath);

      // Add the system routerMap to the current routerMap
      const systemRouterMap = (routerMapObj as { [key: string]: any })[
        RouterMapConstants.ROUTER_MAP_KEY
      ] as Array<RouterInfo>;
      if (systemRouterMap && systemRouterMap.length > 0) {
        this.context.routerMap.unshift(...systemRouterMap);
        Logger.info(this.context.node.getNodeName(), `System routerMap added to the current routerMap`);
      }
    }
  }
}
